
package com.huayu.languo.common.util;

import com.huayu.languo.model.excp.SystemException;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Connection;
import org.jsoup.Connection.Response;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Http发送post请求工具，兼容http和https两种请求类型
 */
public final class HttpUtil {

    private static final Logger LOG = LoggerFactory.getLogger(HttpUtil.class);

    /**
     * 请求超时时间 120s
     */
    private static final int TIME_OUT = 120_000;

    /**
     * Https请求
     */
    private static final String HTTPS = "https";

    private static String httpProtocols = "";

    private HttpUtil() {
    }

    /**
     * 发送Get请求 * * @param url 请求URL * @return 服务器响应对象 * @throws IOException
     */
    public static Response get(String url) throws IOException {
        return get(url, null);
    }

    /**
     * 发送Get请求 * * @param url 请求URL * @param headers 请求头参数 * @return 服务器响应对象 * @throws IOException
     */
    public static Response get(String url, Map<String, String> headers) throws IOException {
        if (null == url || url.isEmpty()) {
            throw new SystemException("The request URL is blank.");
        }
        // 如果是Https请求
        if (url.startsWith(HTTPS)) {
            getTrust();
        }
        Connection connection = Jsoup.connect(url);
        connection.method(Connection.Method.GET);
        connection.timeout(TIME_OUT);
        connection.ignoreHttpErrors(true);
        connection.ignoreContentType(true);
        if (null != headers) {
            connection.headers(headers);
        }
        return connection.execute();
    }

    /**
     * 发送JSON格式参数POST请求 * * @param url 请求路径 * @param params JSON格式请求参数 * @return 服务器响应对象 * @throws IOException
     */
    public static Response post(String url, String params) throws IOException {
        return doPostRequest(url, null, null, null, params);
    }

    /**
     * 发送JSON格式参数POST请求 * * @param url 请求路径 * @param headers 请求头中设置的参数 * @param params JSON格式请求参数 * @return 服务器响应对象 * @throws IOException
     */
    public static Response post(String url, Map<String, String> headers, String params) throws IOException {
        return doPostRequest(url, headers, null, null, params);
    }

    /**
     * 字符串参数post请求 * * @param url 请求URL地址 * @param paramMap 请求字符串参数集合 * @return 服务器响应对象 * @throws IOException
     */
    public static Response post(String url, Map<String, String> paramMap) throws IOException {
        return doPostRequest(url, null, paramMap, null, null);
    }

    /**
     * 带上传文件的post请求 * * @param url 请求URL地址 * @param paramMap 请求字符串参数集合 * @param fileMap 请求文件参数集合 * @return 服务器响应对象 * @throws IOException
     */
    public static Response post(String url, Map<String, String> paramMap, Map<String, File> fileMap) throws IOException {
        return doPostRequest(url, null, paramMap, fileMap, null);
    }

    /**
     * 执行post请求
     *
     * @param url      请求URL地址
     * @param paramMap 请求字符串参数集合
     * @param fileMap  请求文件参数集合
     * @return 服务器响应对象
     * @throws IOException
     */
    private static Response doPostRequest(String url, Map<String, String> headers, Map<String, String> paramMap, Map<String, File> fileMap, String jsonParams) throws IOException {
        if (null == url || url.isEmpty()) {
            throw new SystemException("The request URL is blank.");
        }
        // 如果是Https请求
        if (url.startsWith(HTTPS)) {
            getTrust();
        }
        Connection connection = Jsoup.connect(url);
        connection.method(Connection.Method.POST);
        connection.timeout(TIME_OUT);
        connection.ignoreHttpErrors(true);
        connection.ignoreContentType(true);
        if (null != headers) {
            connection.headers(headers);
        }
        // 收集上传文件输入流，最终全部关闭，添加文件参数
        if (hasData(fileMap)) {
            setFile(connection, fileMap);
        }
        // 设置请求体为JSON格式内容
        else if (null != jsonParams && !jsonParams.isEmpty()) {
            connection.header("Content-Type", "application/json;charset=UTF-8");
            connection.requestBody(jsonParams);
        }
        // 普通表单提交方式
        else {
            connection.header("Content-Type", "application/x-www-form-urlencoded");
        }
        // 添加字符串类参数
        if (hasData(paramMap)) {
            connection.data(paramMap);
        }
        return connection.execute();
    }

    public static void setFile(Connection connection, Map<String, File> fileMap) throws IOException {
        if (null != fileMap && !fileMap.isEmpty()) {
            File file = null;
            Set<Entry<String, File>> set = fileMap.entrySet();
            for (Entry<String, File> e : set) {
                file = e.getValue();
                try (InputStream in = new FileInputStream(file)) {
                    connection.data(e.getKey(), file.getName(), in);
                }
            }
        }
    }

    public static String getEnabledProtocols() throws IOException {
        String supPro = "TLSv1.2";
        SSLSocket socket = null;
        try {
            if (StringUtils.isNotEmpty(httpProtocols)) {
                return httpProtocols;
            }
            SSLContext context = SSLContext.getInstance("TLSv1.2");
            context.init(null, null, null);
            SSLSocketFactory factory = context.getSocketFactory();
            socket = (SSLSocket) factory.createSocket();
            List<String> supportedProtocols = Arrays.asList(socket.getSupportedProtocols());
            LOG.info("【--HTTPS--】Supported Protocols [{}]", supportedProtocols);

            List<String> enabledProtocols = Arrays.asList(socket.getEnabledProtocols());
            LOG.info("【--HTTPS--】Enabled Protocols [{}]", enabledProtocols);

            List<String> needProtocols = Arrays.asList("TLSv1.2,SSLv3,TLSv1,TLSv1.1".split(","));

            for (String needProtocol : needProtocols) {
                if (enabledProtocols.contains(needProtocol)) {
                    supPro = needProtocol;
                    httpProtocols = needProtocol;
                    break;
                }
            }
            LOG.info("【--HTTPS--】init system Protocols [{}]", supPro);
            return supPro;
        } catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
            throw new SystemException(e);
        } finally {
            if (null != socket) {
                socket.close();
            }
        }
    }

    /**
     * 获取服务器信任
     */
    private static void getTrust() {
        try {
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return hostname.equalsIgnoreCase(session.getPeerHost());
                }
            });
            SSLContext context = SSLContext.getInstance(getEnabledProtocols());
            context.init(null, new X509TrustManager[]{new X509TrustManager() {

                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    // Do nothing
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    // Do nothing
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }}, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
        } catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
            LOG.error(e.getMessage(), e);
        }
    }

    private static boolean hasData(Map<?, ?> params) {
        return null != params && !params.isEmpty();
    }
}
